package com.wgo.bpot.server.persist.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.hibernate.tool.hbm2ddl.SchemaExport;

import com.wgo.bpot.common.transport.exception.db.DatabaseException;
import com.wgo.bpot.common.transport.util.Assert;
import com.wgo.bpot.domain.common.CommandHistory;
import com.wgo.bpot.domain.server.ejb.CommandHistoryEjb;
import com.wgo.bpot.server.persist.BasicPersistService;
import com.wgo.bpot.wiring.common.DomainModelConvention;
import com.wgo.bpot.wiring.ejb.EjbModelConventionImpl;

/**
 * @version $Id: HibernatePersistService.java 540 2008-01-09 19:42:31Z petter.eide $
 * @param <T> The Domain Model Super Interface
 */
public class HibernatePersistService<T> extends BasicPersistService<T> {
	
	private static final transient Logger log = Logger.getLogger(HibernatePersistService.class);
	
	public Session getHibernateSession(){
		return HibernateUtil.getSessionFactory().getCurrentSession();
	}
		
	public void createSchema() {
		log.info("Creating database schema...");
		SchemaExport export = new SchemaExport(HibernateUtil.getConfiguration());
//		export.drop(false, true);
		export.create(false, true);
//        deleteRootConcept();
//        log.warn("I am now creating the root RequirementSystem, but this should ideally only happen within the initializationService");
//		try {
//			save(newInstance(getDomainModelConvention().getRootConceptClass()));
//		} catch (Exception e) {
//			log.error(ExceptionUtils.getFullStackTrace(e)); 
//			throw new DatabaseException("Unable to save root concept! " , e );
//		}
		log.info("Database schema created!");
	}


//	private void deleteRootConcepts() {
//		for (Class<? extends T> rootConceptType : this.getDomainModelConvention().getRootConceptClasses()){
//			try {
//				Object root = findDbRootSystem(rootConceptType);
//				if (null != root) {
//					log.info("Deleting already existing root concept");
//					HibernateUtil.getSessionFactory().getCurrentSession().delete(root);
//				} else {
//					log.info("No root concept found of type " + rootConceptType.getSimpleName() + ": No need to delete.");
//				}
//			} catch (DatabaseException e){
//				log.info("No root concept found of type " + rootConceptType.getSimpleName() + ": No need to delete.");
//			}
//		}
//	}

    public <S extends T> void save(S instanceToBeSaved) {
		getHibernateSession().save(instanceToBeSaved);
	}


	/* (non-Javadoc)
	 * @see com.wgo.bpot.server.persist.PersistService#newInstance(java.lang.Class)
	 */
	public Object newInstance(Class apiType) throws InstantiationException, IllegalAccessException {
		Class ejbImplClass = getDomainModelConvention().apiToEjb(apiType);
		Object ejbInstance = null ;
		ejbInstance = ejbImplClass.newInstance();
		return ejbInstance;
	}

	public void saveOrUpdate(Object instanceToBeSaved) {
		getHibernateSession().saveOrUpdate(instanceToBeSaved);
	}
	
	public <S extends T> S findDbRootSystem(Class<S> rootSystemApiClass){
		Assert.assertNotNull(rootSystemApiClass, "The API-System-class cannot be null!");
		
		Class ejbClass = getDomainModelConvention().apiToEjb(rootSystemApiClass) ; //loadServerClass(rootSystemApiClass);
        Criteria crit = getHibernateSession().createCriteria(ejbClass);

        List<S> rootSystemList = crit.list() ; 
		if (rootSystemList.size() < 1 ) {
			throw new DatabaseException ( "RootConcept not found, please initialize persistent layer." );
		}
		if (rootSystemList.size() > 1 ) {
			throw new DatabaseException ( "Inconsistent persistent layer: multiple RootConcepts not found." );
		}
		return rootSystemList.get(0);

	}

	
	
	/* (non-Javadoc)
	 * @see com.wgo.bpot.server.persist.PersistService#getDbInstance(java.lang.Class, java.io.Serializable)
	 */
	@SuppressWarnings("unchecked")
	public <S extends T> S getDbInstance(Class<S> apiType, Serializable id) {
		Class ejbClass = getDomainModelConvention().apiToEjb(apiType);
		S result = null;
		try {
			result = (S)getHibernateSession().get(ejbClass,id);
		} catch (MappingException e) {
			String debugMessage = "Unable to retrieve systems before schemas have been created!" ;
			log.error(debugMessage, e);
			throw new DatabaseException(debugMessage, e);
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	public <S extends T> S getInstance(Class<S> apiType, Serializable id, int linkDepth) {
		S result = getDbInstance(apiType, id);
		result = getDomainModelConvention().convertToDomainModel(result, linkDepth, new Hashtable<String,S>());
		return result;
	}

	public void startTransaction() {
		if (isTransactionActive()) {
			log.warn("A transaction is already running, when a new Transaction is about to be started.");
		}
		getHibernateSession().beginTransaction();
	}

	@SuppressWarnings("unchecked")
	public <S extends T> Collection<S> find(Class<S> apiType, int linkDepth) {
		List<S> list = null;
        try {
            Class ejbClass = getDomainModelConvention().apiToEjb(apiType) ; //loadServerClass(rootSystemApiClass);
            Criteria crit = getHibernateSession().createCriteria(ejbClass);

            Map<String, S> backTrace = new Hashtable<String,S>();
            list = new ArrayList<S>(); 
            for (S object : (List<S>)crit.list() ) { 
            	list.add(getDomainModelConvention().convertToDomainModel(object, linkDepth, backTrace));
            }
        } catch (HibernateException e) {
            throw new DatabaseException("Unable to find " + apiType.getName(), e);
        }
		return list;
	}

	@SuppressWarnings("unchecked")
    public <S extends T> S find(Class<S> apiType, String fieldName, String fieldValue, int linkDepth) {
        S result = null;
        try {
            Class<? extends T> ejbClass = getDomainModelConvention().apiToEjb(apiType);
            Criteria crit = getHibernateSession().createCriteria(ejbClass);
            crit.add(Restrictions.ilike(fieldName,fieldValue));
            S ejbResult = (S)crit.uniqueResult();
            result = getDomainModelConvention().convertToDomainModel(ejbResult,linkDepth, new HashMap<String,S>());
        } catch (HibernateException e){
            throw new DatabaseException("Unable to find " + apiType.getName(), e);
        }
		return result;
	}

	public boolean isTransactionActive() {
		return getHibernateSession().getTransaction().isActive();
	}


	/**
	 * Commit any pending transactions for this session.
	 */
	public void commitTransaction() {
		if (isTransactionActive()){
			getHibernateSession().getTransaction().commit();
		} else {
			log.error("Tried to commit transaction, but no transaction active.");
		}
	}


	public void rollbackTransaction() {
		if (isTransactionActive()){
			getHibernateSession().getTransaction().rollback();
		} else {
			log.error("Tried to roll back transation, but no transaction active.");
		}
	}

	public CommandHistory createCommandHistoryEntry() {
		return new CommandHistoryEjb();
	}

	public List<CommandHistory> retrieveCommandHistory(Class conceptType, Long dbId) {
        Criteria crit = getHibernateSession().createCriteria(CommandHistoryEjb.class);
        if (null != conceptType) {
            crit.add(Restrictions.ilike("conceptClass",conceptType.getName()));
		}
        if (null != dbId) {
            crit.add(Restrictions.eq("conceptDbId",dbId));
		}
        List<CommandHistory> list = new ArrayList<CommandHistory>();
        for (CommandHistory cmdHist : (List<CommandHistory>)crit.list() ) { 
        	list.add(((EjbModelConventionImpl<CommandHistory>)getDomainModelConvention()).convertToDomainModel(cmdHist, 1, new HashMap<String,CommandHistory>()));
        }
        Collections.sort(list);
		return list;
	}

	public Object openSession() {
		return getHibernateSession();
	}

	public void closeSession() {
//		log.warn("Should not be used with Hibernate 3.0.1 and later.");
	}

	/* (non-Javadoc)
	 * @see com.wgo.bpot.server.persist.BasicPersistService#getDomainModelConvention()
	 */
	@Override
	public EjbModelConventionImpl<T> getDomainModelConvention() {
		return (EjbModelConventionImpl<T>)super.getDomainModelConvention();
	}

	/* (non-Javadoc)
	 * @see com.wgo.bpot.server.persist.BasicPersistService#setDomainModelConvention(com.wgo.bpot.wiring.common.DomainModelConvention)
	 */
	public void setDomainModelConvention(EjbModelConventionImpl<T> domainModelConvention) {
		super.setDomainModelConvention(domainModelConvention);
	}

	/* (non-Javadoc)
	 * @see com.wgo.bpot.server.persist.BasicPersistService#setDomainModelConvention(com.wgo.bpot.wiring.common.DomainModelConvention)
	 */
	@Override
	@Deprecated
	public void setDomainModelConvention(DomainModelConvention<T> domainModelConvention) {
		// TODO Auto-generated method stub
		super.setDomainModelConvention(domainModelConvention);
	}
	
}
